#include "EstrategiaAlmacenamientoBloques.h"

namespace Toolkit
{

EstrategiaAlmacenamientoBloques::EstrategiaAlmacenamientoBloques(const float &porcentajeFragmentacion)
{
	this-> porcentajeFragmentacion = porcentajeFragmentacion;
	
	posicionUltimo = 0;
}

EstrategiaAlmacenamientoBloques::~EstrategiaAlmacenamientoBloques()
{
}

unsigned long int EstrategiaAlmacenamientoBloques::guardarDato (Almacenamiento &almacenamiento, Dato &dato,
                    const Clave &clave, const string &nombreAlmacenamiento)
{
	Registro *registro = nuevoRegistro(nombreAlmacenamiento);
	Bloque *bloque = nuevoBloque(nombreAlmacenamiento);
    registro->setDato(&dato);
			
	stringstream serialDato, serialBloque;
	dato.serializar(serialDato);
	unsigned int tamano = serialDato.str().size();
	unsigned long int laPosicion;
	
	if (tamano > bloque->getTamanoBloque()) {
		throw DesbordeDeDatoException();
	}
	
	IndiceEspacio back = espacios.back();
	if (tamano <= back.bytesDisponibles) {
		//Intento ubicarlo en uno de los bloques fragmentados, worst fit
		espacios.pop_back();
		
		laPosicion = back.posicion;		
		almacenamiento.recuperarDato(laPosicion, serialBloque);
		bloque->hidratar(serialBloque);
				
	} else {
		//Intento ubicarlo en el ultimo bloque		
		almacenamiento.recuperarDato(posicionUltimo, serialBloque);
		bloque->hidratar(serialBloque);
		
		if (tamano > bloque->espacioLibre()) {
			//hay que usar un bloque nuevo al final del almacenamiento	
			controlarFragmentacion(*bloque, posicionUltimo);			
			delete bloque;
			
			posicionUltimo++;			
			bloque = nuevoBloque(nombreAlmacenamiento);
		}
		
		laPosicion = posicionUltimo;	
	}	
	
	almacenar(*bloque, *registro, clave, almacenamiento, laPosicion);
	
	delete registro;
	delete bloque;
	
	return laPosicion;
}


unsigned long int EstrategiaAlmacenamientoBloques::modificarDato (Almacenamiento &almacenamiento, Dato &dato, const Clave &clave,
                const unsigned long int posicion, const string &nombreAlmacenamiento)
{
	stringstream serialBloque, serialDato;
	dato.serializar(serialDato);
	
	Bloque *bloque = nuevoBloque(nombreAlmacenamiento);	
	almacenamiento.recuperarDato(posicion, serialBloque);
	bloque->hidratar(serialBloque);
	bloque->remover(clave);
	
	Registro *registro = nuevoRegistro(nombreAlmacenamiento);
	registro->setDato(&dato);
	
	unsigned int tamano = serialDato.str().size();
	if (tamano > bloque->getTamanoBloque()) {
		throw DesbordeDeDatoException();
	}	
	
	unsigned long int laPosicion;
	//si esta en la lista de espacios, lo saco
	removerIndice(posicion);
	
	if (bloque->espacioLibre() >= tamano) {		
		almacenar(*bloque, *registro, clave, almacenamiento, posicion);		
		laPosicion = posicion;
			
	} else {		
		actualizarEliminacionRegistro(*bloque, almacenamiento, posicion);		
		laPosicion = guardarDato(almacenamiento, dato, clave, nombreAlmacenamiento);
				
	} 
	
	delete bloque;
	delete registro;
	
	return laPosicion;
}

void EstrategiaAlmacenamientoBloques::recuperarDato (Almacenamiento &almacenamiento, const unsigned long int posicion, Dato &dato,
                const Clave &clave, const string &nombreAlmacenamiento)
{
	Bloque *bloque = nuevoBloque(nombreAlmacenamiento);
	
	stringstream serializacion;
	almacenamiento.recuperarDato(posicion, serializacion);
	bloque->hidratar(serializacion);	
	
	Registro *registro = dynamic_cast<Registro*>(bloque->buscar(clave));
	registro->hidratarDato(dato);
	
	delete bloque;
}

void EstrategiaAlmacenamientoBloques::eliminarDato (Almacenamiento &almacenamiento, const long unsigned int posicion,
                Dato &dato, const Clave &clave, const string &nombreAlmacenamiento)
{	
	stringstream serialBloque;
	Bloque *bloque = nuevoBloque(nombreAlmacenamiento);
	almacenamiento.recuperarDato(posicion, serialBloque);
	
	bloque->hidratar(serialBloque);
	Registro *registro = dynamic_cast<Registro*>(bloque->remover(clave));
	removerIndice(posicion);
	
	actualizarEliminacionRegistro(*bloque, almacenamiento, posicion);	
	
	registro->hidratarDato(dato);
	
	delete bloque;	
}
                    
/*** metodos privados ***/                    
void EstrategiaAlmacenamientoBloques::controlarFragmentacion(const Bloque &bloque,
 			const unsigned long int &posicion)
{
	IndiceEspacio indice;
	indice.posicion = posicion;
	indice.bytesDisponibles = bloque.espacioLibre();
	
	//si el bloque esta vacío se supone que se marcara como eliminado en el
	//almacenamiento, entonces no estara en la lista.
	if (bloque.cantElementos() > 0 && indice.bytesDisponibles >= 
				porcentajeFragmentacion * bloque.getTamanoBloque()) {
							
		list<IndiceEspacio> aux;
		aux.push_front(indice);
		espacios.merge(aux);
	}	
}

void EstrategiaAlmacenamientoBloques::almacenar(Bloque &bloque, Registro &registro, const Clave &clave, 
    			Almacenamiento &almacenamiento, const long unsigned int &posicion)
{
	stringstream serialBloque;
	
	bloque.agregar(registro, clave);
	bloque.serializar(serialBloque);
	almacenamiento.guardarDato(serialBloque, posicion);
	
	if (posicion != posicionUltimo)	{
		controlarFragmentacion(bloque, posicion);
	}
}

void EstrategiaAlmacenamientoBloques::removerIndice(const long unsigned int &posicion)
{
	list<IndiceEspacio>::iterator it = espacios.begin();
	while (it != espacios.end()) {
		if (it-> posicion == posicion) {
			espacios.erase(it);
			break;
		}	
	}	
}

void EstrategiaAlmacenamientoBloques::actualizarEliminacionRegistro(Bloque &bloque, 
    		Almacenamiento &almacenamiento, const long unsigned int &posicion)
{	
	stringstream serialBloque;
	if (bloque.cantElementos() > 0) {
		//se vuelve a guardar el bloque y se controla fragmentacion
		bloque.serializar(serialBloque);
		almacenamiento.guardarDato(serialBloque, posicion);
		
		controlarFragmentacion(bloque, posicion);
	} else {
		//El bloque quedo vacío, se lo quita del almacenamiento
		almacenamiento.eliminarDato(posicion, serialBloque);
	}
}    			


}
